#pragma once
#ifndef LIT__SCRIPT__ENGINE__HH__
#define LIT__SCRIPT__ENGINE__HH__

#include "LitScriptPrereqs.h"
#include "LitScriptModule.h"
#include "LitScriptContext.h"
#include "LitScriptEngineNativeAPI.h"

namespace Lit
{
	class ScriptEngine
	{
	public:
		typedef asIScriptEngine Type;
		typedef RefPtr<Type>	Ptr;
		typedef std::vector<FilePath> FilePathVector;
		
	protected:
		Ptr								mEngine;
		std::vector<ScriptModulePtr>	mModules;
		std::vector<ScriptContextPtr>	mContexts;
		
		RefPtr<Ogre::Log>				mLog;
		NativeScriptAPIPtr				mApi;
		DWORD							mTimeOut;
		static RefPtr<Ogre::Log>		mRegLog;


		FilePathVector mSearchDirs;
	public:
		void logMessage( const std::string &msg, Ogre::LogMessageLevel lml = Ogre::LML_NORMAL, bool maskDebug = false );
		static RefPtr<Ogre::Log> getLog();

		typedef void(*PFNMessageCallback)( const asSMessageInfo* msg, void * param );

		bool isIncludableContent( const FilePath& path ) const
		{
			for( auto i=mSearchDirs.begin(); i!=mSearchDirs.end(); i++ )
			{
				if( FilePath( (*i) + "/" + path ).exists() )
					return true;
			}
			return false;
		}
		std::string getIncludePath( const FilePath& path ) const
		{
			for( auto i=mSearchDirs.begin(); i!=mSearchDirs.end(); i++ )
			{
				if( FilePath( (*i) + "/" + path ).exists() )
					return FilePath( (*i) + "/" + path ).str();
			}
			return "";
		}
		void addIncludeSearchPath( const FilePath& path )
		{
			mSearchDirs.push_back( path );
		}

		void removeIncludeSearchPath( const FilePath& path )
		{
			for( auto i=mSearchDirs.begin(); i!=mSearchDirs.end(); i++ )
			{
				if( (*i).str() == path.str() ){ mSearchDirs.erase(i); return; }
			}
		}

		FilePathVector& getIncludeSearchPath(){ return mSearchDirs; }
		const FilePathVector& getIncludeSearchPath() const{ return mSearchDirs; }

	protected:
		static void MessageCallback(const asSMessageInfo *msg, void *param);

	public:
		ScriptEngine(Ogre::SceneManager * mgr, Ogre::Log * log = NULL);
		ScriptEngine(const ScriptEngine& rhs);
		~ScriptEngine(void);

		void setTimeOut( DWORD timeOut ){ mTimeOut = timeOut; }
		DWORD getTimeOut() const{ return mTimeOut; }

		bool checkDef( int ret, const std::string &declaration );
		
		static std::string trReturnCode( int ret_code )
		{
			switch( ret_code )
			{
				case asSUCCESS: return "Success";
				case asERROR: return "Error";
				case asCONTEXT_ACTIVE: return "Context";
				case asCONTEXT_NOT_FINISHED: return "Context Not Finished";
				case asCONTEXT_NOT_PREPARED : return "Context Not Prepared";
				case asINVALID_ARG: return "Invalid Arg";
				case asNO_FUNCTION : return "No Function";
				case asNOT_SUPPORTED  : return "Not Supported";
				case asINVALID_NAME : return "Invalid Name";
				case asNAME_TAKEN  : return "Name Taken";
				case asINVALID_DECLARATION  : return "Invalid Declaration";
				case asINVALID_OBJECT   : return "Invalid Object";
				case asINVALID_TYPE    : return "Invalid Type";
				case asALREADY_REGISTERED : return "Already Registered";
				case asMULTIPLE_FUNCTIONS  : return "Multiple Functions";
				case asNO_MODULE : return "No Module";
				case asNO_GLOBAL_VAR  : return "No Global Var";
				case asINVALID_CONFIGURATION  : return "Invalid Configuration";
				case asINVALID_INTERFACE : return "Invalid Interface";
				case asCANT_BIND_ALL_FUNCTIONS  : return "Cant Bind All Functions";
				case asLOWER_ARRAY_DIMENSION_NOT_REGISTERED : return "Lower Array Dimension Not Registered";
				case asWRONG_CONFIG_GROUP      : return "Wrong Config Group";
				case asCONFIG_GROUP_IS_IN_USE : return "Config Group Is In Use";
				case asILLEGAL_BEHAVIOUR_FOR_TYPE : return "Illegal Behaviour For Type";
				case asWRONG_CALLING_CONV  : return "Wrong Calling Convention";
				case asBUILD_IN_PROGRESS : return "Build In Progress";
				case asINIT_GLOBAL_VARS_FAILED  : return "Initialisation Of Global Variables Failed";
				case asOUT_OF_MEMORY : return "Out Of Memory";
				default : return "Unknown";
			};
		}
		int registerEnum( const std::string &name ){ return mEngine->RegisterEnum( name.c_str() ); }
		int registerEnumValue( const std::string &type, const std::string &name, int val ){ return mEngine->RegisterEnumValue( type.c_str(), name.c_str(), val ); }
		
		static ScriptContextState runTimeOut( ScriptContextPtr ctx, DWORD timeOut = 1000 );
		static void LineCallback(asIScriptContext *ctx, DWORD *timeOut);
		bool setDefaultNamespace( const std::string &ns ){ if( !mEngine ) return false; return (mEngine->SetDefaultNamespace( ns.c_str() )>=0); }

		void setMessageCallback( const asSFuncPtr& func, void * obj = 0, asDWORD callConvention = asCALL_CDECL );
		void setMessageCallback( );
		
		NativeScriptAPICoreFuncsPtr& getCore();
		const NativeScriptAPICoreFuncsPtr& getCore() const;

		NativeScriptAPIPtr& getApi();
		const NativeScriptAPIPtr& getApi() const;

		bool registerGlobalFunction( const std::string &func_declaration, const asSFuncPtr &func );
        bool registerClassMem(const std::string& class_name, const std::string& function_definition, const asSFuncPtr &funcPointer);
        template <class class_type> bool registerClass(const std::string& class_name)
		{
			if (mEngine->RegisterObjectType(class_name.c_str(), sizeof(class_type), asOBJ_VALUE | asOBJ_POD)<0)
			{
				logMessage( "Can't register class " + class_name, Ogre::LML_CRITICAL );
				return false;
			}
			return true;
		}

		void initialise( Ogre::SceneManager * mgr,Ogre::Log * log = NULL );
		void destroy();

		static std::string trFlags( ScriptModuleFlags flags )
		{
			if( flags == SMF_ALWAYS_CREATE ) return "SMF_ALWAYS_CREATE";
			else if( flags == SMF_CREATE_IF_NOT_EXISTS ) return "SMF_CREATE_IF_NOT_EXISTS";
			
			return "SMF_ONLY_IF_EXISTS";
		}

		const Ptr& data() const;
		Ptr& data();
		
		ScriptContextPtr createContext( );
		void releaseContext( ScriptContextPtr context, bool destroy=false );
		
		ScriptModulePtr buildStreamAsModule( const std::string &name, StreamPtr stream, ScriptModuleFlags flags = SMF_CREATE_IF_NOT_EXISTS );
		
		bool runModule( const std::string &name, const std::string &entry_point = "main", ScriptContextPtr context = ScriptContextPtr() );
		bool runModule( const ScriptModulePtr &module, const std::string &entry_point = "main", ScriptContextPtr context = ScriptContextPtr() );

		ScriptContextPtr findFreeContext() const;
		ScriptContextPtr nextFreeContext();

		ScriptModulePtr findModule( const std::string &name );
		ScriptModulePtr createModule( const std::string &name, ScriptModuleFlags flags = SMF_CREATE_IF_NOT_EXISTS );
		void releaseModule( ScriptModulePtr module );
	};
};

#define SC_CHECK_DEF( engine_ptr, expr ) engine_ptr->checkDef( expr, #expr )
#define SC_OBJECT( engine_ptr, class_name, class_type, flags ) engine_ptr->RegisterObjectType( class_name, sizeof(class_type), flags )
#define SC_OBJECT_BEHAV( engine_ptr, class_name, behav, declaration, func, call_convention ) engine_ptr->RegisterObjectBehaviour(class_name, behav,  declaration, func, call_convention)
#define SC_OBJECT_CONSTRUCT( engine_ptr, class_name, declaration, func, call_convention ) engine_ptr->RegisterObjectBehaviour(class_name, asBEHAVE_CONSTRUCT,  declaration, func, call_convention)
#define SC_OBJECT_DESTRUCT( engine_ptr, class_name, declaration, func, call_convention ) engine_ptr->RegisterObjectBehaviour(class_name, asBEHAVE_DESTRUCT,  declaration, func, call_convention)
#define SC_OBJECT_METHOD( engine_ptr, class_name, declaration, func, call_convention ) engine_ptr->RegisterObjectMethod(class_name, declaration, func, call_convention)
#define SC_OBJECT_PROP( engine_ptr, class_name, declaration, offset ) engine_ptr->RegisterObjectProperty(class_name, declaration, offset)
	
#endif